<?php

namespace tools\ftp;


class Ftp
{
    private string $host; // 远程服务器地址
    private string $user; // ftp用户名
    private string $password; // ftp密码
    private string $port; // ftp登录端口
    private string $error; // 最后失败时的错误信息
    private $connect; // ftp登录资源

    /**
     * 初始化配置参数
     * Ftp constructor.
     * @param string $host 远程服务器地址
     * @param string $user ftp用户名
     * @param string $password ftp密码
     * @param string $port ftp登录端口
     */
    public function __construct(string $host, string $user, string $password, string $port = '21')
    {
        $this->host = $host;
        $this->user = $user;
        $this->password = $password;
        $this->port = $port;
    }

    /**
     * 连接及登录ftp
     * @return bool
     */
    public function connect() : bool
    {
        $this->connect = @ftp_connect($this->host, $this->port);
        if ($this->connect == false) {
            $this->error = '主机连接失败';
            return false;
        }

        $result = @ftp_login($this->connect, $this->user, $this->password);
        if ($result == false) {
            $this->error = '服务器登录失败';
            return false;
        }

        return true;
    }

    /**
     * 上传文件到ftp服务器
     * @param string $localFile 本地文件路径
     * @param string $remoteFile 服务器文件地址
     * @param string $mode 上传模式（ascii和binary，或者填auto来根据文件后缀自动判断）
     * @return bool
     */
    public function upload($localFile = '', $remoteFile = '', $mode = 'auto') : bool
    {
        if (!file_exists($localFile)) {
            $this->error = '本地文件不存在';
            return false;
        }
        if ($mode == 'auto') {
            // 如果本地文件没有后缀，则默认后缀为txt
            $dot = strrpos($localFile, '.');
            if ($dot === false) {
                $ext = 'txt';
            } else {
                $ext = substr($localFile, $dot + 1);
            }
            // 根据文件后缀内容来判断使用哪种上传模式
            $asciiExt = ['txt', 'text', 'php', 'phps', 'php4', 'js', 'css', 'htm', 'html', 'phtml', 'shtml', 'log', 'xml'];
            if (in_array($ext, $asciiExt, true)) {
                $mode = 'ascii';
            } else {
                $mode = 'binary';
            }
        }
        // 创建文件夹
        $result = $this->createRemoteDir($remoteFile);
        if ($result === false) {
            return false;
        }

        $mode = ($mode == 'ascii') ? FTP_ASCII : FTP_BINARY;
        $result = @ftp_put($this->connect, $remoteFile, $localFile, $mode); // 同步上传
        if ($result === false) {
            $this->error = '文件上传失败';
            return false;
        }
        return true;
    }

    /**
     * 获取上传错误信息
     * @return string
     */
    public function getErrorMsg()
    {
        return $this->error;
    }

    /**
     * 关闭ftp连接
     * @return bool
     */
    public function close()
    {
        return $this->connect() ? @ftp_close($this->connect) : false;
    }

    /**
     * ftp创建多级目录
     * @param string $remoteFile ftp服务器文件路径
     * @param string $permissions 权限，比如'0755'
     * @return bool 创建成功返回true，否则返回false
     */
    private function createRemoteDir(string $remoteFile, $permissions = '')
    {
        $remoteDir = dirname($remoteFile);
        $pathArr = explode('/', $remoteDir); // 取目录数组
        $pathDiv = count($pathArr); // 取层数
        foreach ($pathArr as $val) { // 创建目录
            if (@ftp_chdir($this->connect, $val) == false) {
                $tmp = @ftp_mkdir($this->connect, $val);
                if ($tmp == false) {
                    $this->error = '目录创建失败，请检查权限及路径是否正确！';
                    return false;
                }
                if ($permissions !== '') {
                    // 修改目录权限
                    $result = @ftp_chmod($this->connect, $val, $permissions);
                    if (!$result) {
                        $this->error = '权限设置错误';
                        return false;
                    }
                }
                @ftp_chdir($this->connect, $val);
            }
        }

        for ($i = 0; $i < $pathDiv; $i++) { // 回退到根，因为上面的目录切换导致当前目录不在根目录
            @ftp_cdup($this->connect);
        }

        return true;
    }
}